package ru.usu.gv.gui.clustering;

import ru.usu.gv.core.GraphCanvas;
import ru.usu.gv.graph.Vertex;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;

import java.util.ArrayList;
import java.util.List;

/**
 * @author spupyrev
 * 17.11.2008
 */
public class Cluster
{
	private List<Cluster> subClusters;
	private List<Vertex> vertexes;
	private BinaryGraph binaryGraph;

	public Cluster(BinaryGraph binaryGraph)
	{
		this.binaryGraph = binaryGraph;
	}

	public void init(List<Cluster> subClusters, List<Vertex> vertexes, BinaryGraph binaryGraph)
	{
		this.binaryGraph = binaryGraph;
		this.subClusters = subClusters;
		this.vertexes = vertexes;
	}

	public int getSize()
	{
		if (containsVertexes())
			return getVertexes().size();
		else
			return getSubClusters().size();
	}

	public boolean containsVertexes()
	{
		return vertexes != null;
	}

	public boolean containsClusters()
	{
		return (!containsVertexes());
	}

	public List<Vertex> getVertexes()
	{
		return vertexes;
	}

	public List<Cluster> getSubClusters()
	{
		return subClusters;
	}

	public BinaryGraph getBinaryGraph()
	{
		return binaryGraph;
	}

	public void addVertex(Vertex v)
	{
		if (subClusters != null)
			throw new RuntimeException("Cluster already contains subClusters!");

		if (vertexes == null)
			vertexes = new ArrayList<Vertex>();

		vertexes.add(v);
	}

	public void addSubCluster(Cluster c)
	{
		if (vertexes != null)
			throw new RuntimeException("Cluster already contains vertexes!");

		if (subClusters == null)
			subClusters = new ArrayList<Cluster>();

		subClusters.add(c);
	}

	public BinaryGraph constructSubBinaryGraph(List<Integer> indexes)
	{
		IntSet needIndexes = new IntOpenHashSet(indexes);
		Int2IntMap old2New = new Int2IntOpenHashMap();
		int c = 0;
		for (int i = 0; i < binaryGraph.n; i++)
			if (needIndexes.contains(i))
				old2New.put(i, c++);

		BinaryGraph graph = new BinaryGraph();

		graph.n = indexes.size();
		graph.links = new int[graph.n][];
		graph.weights = new int[graph.n][];

		for (int i = 0; i < binaryGraph.n; i++)
			if (needIndexes.contains(i))
			{
				IntList l = new IntArrayList();
				for (int j = 0; j < binaryGraph.links[i].length; j++)
					if (needIndexes.contains(binaryGraph.links[i][j]))
						l.add(j);

				graph.links[old2New.get(i)] = new int[l.size()];
				graph.weights[old2New.get(i)] = new int[l.size()];
				for (int j = 0; j < l.size(); j++)
				{
					graph.links[old2New.get(i)][j] = old2New.get(binaryGraph.links[i][l.getInt(j)]);
					graph.weights[old2New.get(i)][j] = binaryGraph.weights[i][l.getInt(j)];
					graph.totalWeight += graph.weights[old2New.get(i)][j];
				}
			}

		return graph;
	}

	private List<Vertex> subVertexes;
	private int subVertexesSize = -1;

	public List<Vertex> extractAllVertexes()
	{
		if (subVertexes == null)
		{
			subVertexes = new ArrayList<Vertex>();
			if (containsVertexes())
				subVertexes.addAll(getVertexes());
			else
				for (Cluster subCluster : subClusters)
					subVertexes.addAll(subCluster.extractAllVertexes());
		}

		return subVertexes;
	}

	public int getVertexCount()
	{
		if (subVertexesSize == -1)
		{
			if (subVertexes != null)
			{
				subVertexesSize = subVertexes.size();
			}
			else
			{
				subVertexesSize = ClusterHelper.calculateVertexCount(this);
			}
		}
		return subVertexesSize;
	}

	private int level;

	public void postInit(int level)
	{
		this.level = level;

		if (containsClusters())
		{
			for (int i = 0; i < subClusters.size(); i++)
				subClusters.get(i).postInit(level + 1);
		}
	}

	public int getLevel()
	{
		return level;
	}

	@Override
	public String toString()
	{
		return print("");
	}

	private String print(String prefix)
	{
		StringBuilder res = new StringBuilder();
		if (subClusters != null)
		{
			//root or intermideate
			res.append(prefix + "cluster [" + subClusters.size() + "]:\n");
			for (Cluster subCluster : subClusters)
				res.append(subCluster.print(prefix + "  "));
		}
		else if (vertexes != null)
		{
			//leaf
			StringBuilder s = new StringBuilder();
			for (Vertex v : vertexes)
			{
				String label = null;
				if (GraphCanvas.getInstance() != null)
					label = v.findUIVertex().getLabel();
				s.append(label == null ? v.getIndex() : label).append(" ");
			}
			res.append(prefix + "leaf [" + vertexes.size() + "](" + s + ")\n");
		}
		else
			throw new RuntimeException("Either subClusters or vertexes should be not null!");

		return res.toString();
	}

	public void outputStatistics()
	{
		System.out.println("Cluster statistics:");
		System.out.println("	max-depth = " + ClusterHelper.calculateDepth(this));
		System.out.println("	subClusters = " + ClusterHelper.calculateSubClustersCount(this));
		System.out.println("	subVertexes = " + getVertexCount());
	}
}
